import os
import sys
import shutil
from pathlib import Path
from typing import List
from gglobal import logger

PROJECT_ROOT = Path(__file__).parent.resolve()
ENTRY_SCRIPT = PROJECT_ROOT / "main.py"
APP_NAME_DEFAULT = "MooeTool"
OUTPUT_ROOT = PROJECT_ROOT / "build"
FINAL_DIR_NAME = "mooetool_build"
DIST_ROOT = Path("C:/MooeToolBuild").resolve()


def generate_args(app_name: str, additional_paths: List[str]) -> List[str]:
    args: List[str] = [
        "--noconfirm",
        "--clean",
        "--onedir",
        "--name",
        app_name,
        "--distpath",
        str(DIST_ROOT),
        "--workpath",
        str(OUTPUT_ROOT / "build_work"),
        "--specpath",
        str(OUTPUT_ROOT / "build_spec"),
        "--windowed",
        "--noupx",
        "--uac-admin",
        "--icon",
        str(PROJECT_ROOT / "resource" / "qrc" / "bitbug_favicon.ico"),
        "--hidden-import=PySide2",
        "--hidden-import=PySide2.QtCore",
        "--hidden-import=PySide2.QtGui",
        "--hidden-import=PySide2.QtWidgets",
        "--hidden-import=PySide2.QtOpenGL",
        "--hidden-import=numpy",
        "--hidden-import=pyqtgraph",
        "--hidden-import=pyqtgraph.opengl",
        "--hidden-import=OpenGL",
        "--hidden-import=OpenGL.GL",
        "--hidden-import=OpenGL.GLU",
        "--hidden-import=OpenGL.arrays.arraydatatype",
        "--hidden-import=rosbags",
        "--hidden-import=rosbags.typesys",
        "--hidden-import=rosbags.highlevel",
        "--hidden-import=rosbags.serde",
        "--hidden-import=lz4.frame",
        "--hidden-import=websockets",
        "--hidden-import=websockets.legacy",
        "--hidden-import=websockets.protocol",
        # 不收集整个 PySide2 子模块，避免引入 QtWebEngine 及其 locales 依赖
        "--collect-submodules=numpy",
        "--collect-submodules=pyqtgraph",
        "--collect-submodules=OpenGL",
        "--collect-submodules=rosbags",
        "--collect-submodules=websockets",
        "--collect-data=PySide2",
        "--collect-data=numpy",
        "--collect-data=pyqtgraph",
        "--collect-data=OpenGL",
        # 显式排除 QtWebEngine 家族，避免 PyInstaller 钩子寻找 qtwebengine_locales
        "--exclude-module=PySide2.QtWebEngine",
        "--exclude-module=PySide2.QtWebEngineCore",
        "--exclude-module=PySide2.QtWebChannel",
        str(ENTRY_SCRIPT),
    ]
    for p in additional_paths:
        args.extend(["--paths", p])
    return args


def get_site_paths() -> List[str]:
    try:
        import site
        paths = [p for p in site.getsitepackages() if os.path.isdir(p)]
        return paths
    except Exception:
        return []


def clean_dist(app_name: str) -> None:
    dist_dir = DIST_ROOT / app_name
    if dist_dir.exists():
        try:
            shutil.rmtree(dist_dir)
        except Exception:
            pass


def copy_directory(src: Path, dst: Path) -> None:
    if not src.exists():
        logger.warning(f"源目录不存在 {src}")
        return
    if dst.exists():
        try:
            shutil.rmtree(dst)
        except Exception:
            pass
    shutil.copytree(src, dst)
    logger.info(f"复制目录: {src} -> {dst}")


def _final_output_dir() -> Path:
    return DIST_ROOT / FINAL_DIR_NAME


def post_copy(app_name: str) -> Path:
    dist_dir = DIST_ROOT / app_name
    final_dir = _final_output_dir()
    try:
        if final_dir.exists():
            shutil.rmtree(final_dir)
        shutil.move(str(dist_dir), str(final_dir))
    except Exception:
        pass
    dist_dir = final_dir
    msg_src = PROJECT_ROOT / "apis" / "ros1" / "msg"
    vehicle_src = PROJECT_ROOT / "apis" / "ros1" / "vehicle"
    resource_src = PROJECT_ROOT / "resource"
    config_src = PROJECT_ROOT / "config"
    msg_dst = dist_dir / "apis" / "ros1" / "msg"
    vehicle_dst = dist_dir / "apis" / "ros1" / "vehicle"
    resource_dst = dist_dir / "resource"
    config_dst = dist_dir / "config"
    msg_dst.parent.mkdir(parents=True, exist_ok=True)
    vehicle_dst.parent.mkdir(parents=True, exist_ok=True)
    resource_dst.parent.mkdir(parents=True, exist_ok=True)
    config_dst.parent.mkdir(parents=True, exist_ok=True)
    copy_directory(msg_src, msg_dst)
    copy_directory(vehicle_src, vehicle_dst)
    copy_directory(resource_src, resource_dst)
    copy_directory(config_src, config_dst)
    (dist_dir / "log").mkdir(exist_ok=True)
    (dist_dir / "db").mkdir(exist_ok=True)
    logger.info("打包后复制与创建目录完成")
    return dist_dir


def build_with_pyinstaller(app_name: str, clean: bool) -> Path:
    if clean:
        clean_dist(app_name)
    additional_paths = get_site_paths()
    args = generate_args(app_name, additional_paths)
    try:
        logger.info("开始使用PyInstaller打包")
        import PyInstaller.__main__ as pymain
        pymain.run(args)
    except Exception as e:
        logger.error(f"PyInstaller打包失败: {e}")
        raise
    return DIST_ROOT / app_name


def main() -> int:
    try:
        import argparse
        parser = argparse.ArgumentParser()
        parser.add_argument("--name", default=APP_NAME_DEFAULT)
        parser.add_argument("--no-clean", action="store_true")
        parser.add_argument("--skip-build", action="store_true")
        parser.add_argument("--print-args", action="store_true")
        parser.add_argument("--safe-dist", action="store_true")
        opts = parser.parse_args()
        app_name = opts.name
        global DIST_ROOT
        def _is_ascii(s: str) -> bool:
            try:
                s.encode("ascii")
                return True
            except Exception:
                return False
        if opts.safe_dist or not _is_ascii(str(OUTPUT_ROOT)):
            DIST_ROOT = Path("C:/MooeToolBuild").resolve()
        else:
            DIST_ROOT = OUTPUT_ROOT
        if opts.print_args:
            args = generate_args(app_name, get_site_paths())
            for a in args:
                print(a)
            return 0
        if not opts.skip_build:
            build_with_pyinstaller(app_name, clean=not opts.no_clean)
        dist_dir = post_copy(app_name)
        logger.info(f"输出目录: {dist_dir}")
        return 0
    except Exception as e:
        logger.error(f"执行失败: {e}")
        return 1


if __name__ == "__main__":
    sys.exit(main())